import os
import json
import _pickle as cPickle
from PIL import Image
import re
import base64
import numpy as np
import csv
import sys
import time
import pprint
import logging
import pandas as pd

import torch
from torch.utils.data import Dataset
from external.pytorch_pretrained_bert import BertTokenizer

from common.utils.zipreader import ZipReader
from common.utils.create_logger import makedirsExist

from pycocotools.coco import COCO

csv.field_size_limit(sys.maxsize)
FIELDNAMES = ['image_id', 'image_w', 'image_h', 'num_boxes', 'boxes', 'features']


class VQA(Dataset):
    def __init__(self, image_set, root_path, data_path, answer_vocab_file, use_imdb=True,
                 with_precomputed_visual_feat=False, boxes="36",
                 transform=None, test_mode=False,
                 zip_mode=False, cache_mode=False, cache_db=True, ignore_db_cache=True,
                 tokenizer=None, pretrained_model_name=None, batch_size = 4,
                 add_image_as_a_box=False, mask_size=(14, 14),
                 aspect_grouping=False, **kwargs):
        """
        Visual Question Answering Dataset

        :param image_set: image folder name
        :param root_path: root path to cache database loaded from annotation file
        :param data_path: path to vcr dataset
        :param transform: transform
        :param test_mode: test mode means no labels available
        :param zip_mode: reading images and metadata in zip archive
        :param cache_mode: cache whole dataset to RAM first, then __getitem__ read them from RAM
        :param ignore_db_cache: ignore previous cached database, reload it from annotation file
        :param tokenizer: default is BertTokenizer from pytorch_pretrained_bert
        :param add_image_as_a_box: add whole image as a box
        :param mask_size: size of instance mask of each object
        :param aspect_grouping: whether to group images via their aspect
        :param kwargs:
        """
        super(VQA, self).__init__()

        assert not cache_mode, 'currently not support cache mode!'

        # 重新写加载数据
        ######11.22注释
        # text = {
        #     "train": "tweets_DevSet_2015.txt",
        #     "val": "tweets_TestSet_2015.txt",
        # }
        ######11.22添加
        text = {
            "train": "train_mediaeval_filter15.csv",
            "val": "test_mediaeval_filter15.csv",
            "train2": "train_ticnn_filter15.csv",
            "val2": "test_ticnn_filter15.csv",
            "train3": "train_ticnn_filter15.csv",
            "val3": "test_ticnn_filter15.csv",
            "train4": "train_phemedata_filter15.csv",
            "val4": "test_phemedata_filter15.csv",
            "val4_adv": "test_phemedata_filter15_adv.csv",
            "train5": "test_phemedata_filter15.csv",###鲁棒性实验
            "val5": "test_phemedata_filter15.csv",###鲁棒性实验
            "train6": "test_mediaeval_filter15.csv",###鲁棒性实验
            "val6": "test_mediaeval_filter15.csv",###鲁棒性实验
            "m0_0": "Mediaeval_filter15_2fold_split0_0.csv",#显著性测试，2fold cross valid test, 5轮
            "m0_1": "Mediaeval_filter15_2fold_split0_1.csv",
            "m1_0": "Mediaeval_filter15_2fold_split1_0.csv",
            "m1_1": "Mediaeval_filter15_2fold_split1_1.csv",
            "m2_0": "Mediaeval_filter15_2fold_split2_0.csv",
            "m2_1": "Mediaeval_filter15_2fold_split2_1.csv",
            "m3_0": "Mediaeval_filter15_2fold_split3_0.csv",
            "m3_1": "Mediaeval_filter15_2fold_split3_1.csv",
            "m4_0": "Mediaeval_filter15_2fold_split4_0.csv",
            "m4_1": "Mediaeval_filter15_2fold_split4_1.csv",
            "p0_0": "phemedata_filter15_2fold_split0_0.csv",#显著性测试，2fold cross valid test, 5轮
            "p0_1": "phemedata_filter15_2fold_split0_1.csv",
            "p1_0": "phemedata_filter15_2fold_split1_0.csv",
            "p1_1": "phemedata_filter15_2fold_split1_1.csv",
            "p2_0": "phemedata_filter15_2fold_split2_0.csv",
            "p2_1": "phemedata_filter15_2fold_split2_1.csv",
            "p3_0": "phemedata_filter15_2fold_split3_0.csv",
            "p3_1": "phemedata_filter15_2fold_split3_1.csv",
            "p4_0": "phemedata_filter15_2fold_split4_0.csv",
            "p4_1": "phemedata_filter15_2fold_split4_1.csv",
            "pte1": "phemedata_filter15_10fold_1_test.csv",
            "ptr1": "phemedata_filter15_10fold_1_train.csv",
            "pte2": "phemedata_filter15_10fold_2_test.csv",
            "ptr2": "phemedata_filter15_10fold_2_train.csv",
            "pte3": "phemedata_filter15_10fold_3_test.csv",
            "ptr3": "phemedata_filter15_10fold_3_train.csv",
            "pte4": "phemedata_filter15_10fold_4_test.csv",
            "ptr4": "phemedata_filter15_10fold_4_train.csv",
            "pte5": "phemedata_filter15_10fold_5_test.csv",
            "ptr5": "phemedata_filter15_10fold_5_train.csv",
            "pte6": "phemedata_filter15_10fold_6_test.csv",
            "ptr6": "phemedata_filter15_10fold_6_train.csv",
            "pte7": "phemedata_filter15_10fold_7_test.csv",
            "ptr7": "phemedata_filter15_10fold_7_train.csv",
            "pte8": "phemedata_filter15_10fold_8_test.csv",
            "ptr8": "phemedata_filter15_10fold_8_train.csv",
            "pte9": "phemedata_filter15_10fold_9_test.csv",
            "ptr9": "phemedata_filter15_10fold_9_train.csv",
            "pte0": "phemedata_filter15_10fold_0_test.csv",
            "ptr0": "phemedata_filter15_10fold_0_train.csv",
            "mte1": "Mediaeval_filter15_10fold_1_test.csv",
            "mtr1": "Mediaeval_filter15_10fold_1_train.csv",
            "mte2": "Mediaeval_filter15_10fold_2_test.csv",
            "mtr2": "Mediaeval_filter15_10fold_2_train.csv",
            "mte3": "Mediaeval_filter15_10fold_3_test.csv",
            "mtr3": "Mediaeval_filter15_10fold_3_train.csv",
            "mte4": "Mediaeval_filter15_10fold_4_test.csv",
            "mtr4": "Mediaeval_filter15_10fold_4_train.csv",
            "mte5": "Mediaeval_filter15_10fold_5_test.csv",
            "mtr5": "Mediaeval_filter15_10fold_5_train.csv",
            "mte6": "Mediaeval_filter15_10fold_6_test.csv",
            "mtr6": "Mediaeval_filter15_10fold_6_train.csv",
            "mte7": "Mediaeval_filter15_10fold_7_test.csv",
            "mtr7": "Mediaeval_filter15_10fold_7_train.csv",
            "mte8": "Mediaeval_filter15_10fold_8_test.csv",
            "mtr8": "Mediaeval_filter15_10fold_8_train.csv",
            "mte9": "Mediaeval_filter15_10fold_9_test.csv",
            "mtr9": "Mediaeval_filter15_10fold_9_train.csv",
            "mte0": "Mediaeval_filter15_10fold_0_test.csv",
            "mtr0": "Mediaeval_filter15_10fold_0_train.csv",
            "attack1": "test_phemedata_filter15_attack.csv",   #biran
            "attack2": "train_phemedata_filter15_attack.csv",
            "attack4": "train_phemedata_filter15_attackall.csv",
            "attack3": "train_phemedata_filter15_attack1.csv"

        }
        self.periodStrip = re.compile("(?!<=\d)(\.)(?!\d)")
        self.commaStrip = re.compile("(\d)(\,)(\d)")
        self.punct = [';', r"/", '[', ']', '"', '{', '}',
                      '(', ')', '=', '+', '\\', '_', '-',
                      '>', '<', '@', '`', ',', '?', '!']

        self.use_imdb = use_imdb
        self.boxes = boxes
        self.test_mode = test_mode
        self.with_precomputed_visual_feat = with_precomputed_visual_feat
        self.data_path = data_path
        self.root_path = root_path

        print("image_set:", image_set)
        self.image_sets = [iset.strip() for iset in image_set.split('+')]
        self.tweets_fn = [os.path.join(data_path, text[iset]) for iset in self.image_sets]
        #self.label_fn = [os.path.join(data_path, label[iset])) for iset in self.image_sets]
        print("tweets_fn:", self.tweets_fn)
        self.box_fn = [os.path.join(data_path, "vgbua_res101_precomputed/")]
        self.image_fn = [os.path.join(data_path, "images_final/")]

        self.box_bank = {}

        self.transform = transform
        self.zip_mode = zip_mode
        self.cache_mode = cache_mode
        self.cache_db = cache_db
        self.ignore_db_cache = ignore_db_cache
        self.aspect_grouping = aspect_grouping
        self.cache_dir = os.path.join(root_path, 'cache')
        self.add_image_as_a_box = add_image_as_a_box
        self.mask_size = mask_size
        self.batch_size = batch_size
        with open(answer_vocab_file, 'r', encoding='utf8') as f:
            self.answer_vocab = [w.lower().strip().strip('\r').strip('\n').strip('\r') for w in f.readlines()]
            self.answer_vocab = list(filter(lambda x: x != '', self.answer_vocab))
            if not self.use_imdb:
                self.answer_vocab = [self.processPunctuation(w) for w in self.answer_vocab]
        if not os.path.exists(self.cache_dir):
            makedirsExist(self.cache_dir)
        self.tokenizer = tokenizer if tokenizer is not None \
            else BertTokenizer.from_pretrained(
            'bert-base-uncased' if pretrained_model_name is None else pretrained_model_name,
            cache_dir=self.cache_dir)

        if zip_mode:
            self.zipreader = ZipReader()

        self.database = self.load_annotations()

        print("self.database.size():",len(self.database))

        if self.aspect_grouping:
            self.group_ids = self.group_aspect(self.database)

    @property
    def data_names(self):
        if self.test_mode:
            return ['image', 'boxes', 'im_info', 'text']        #biran
        else:
            return ['image', 'boxes', 'im_info', 'text', 'label']

    def __getitem__(self, index):
        # 重写数据读入
        idb = self.database[index]
        image = self._load_image(idb['image_fn'])  # 图片
        boxes_data = self._load_json(idb['box_fn'])
        boxes = torch.as_tensor(np.copy(np.frombuffer(self.b64_decode(boxes_data['boxes']), dtype=np.float32).reshape(
            (boxes_data['num_boxes'], -1))))
        boxes_features = torch.as_tensor(np.copy(np.frombuffer(self.b64_decode(boxes_data['features']), dtype=np.float32).reshape(
            (boxes_data['num_boxes'], -1))))

        text_tokens = self.tokenizer.tokenize(idb['text'])  # 文本
        text_ids = self.tokenizer.convert_tokens_to_ids(text_tokens)

        answers=[]
        if not self.test_mode:
            answers.append(idb['label'])
            # print('len(answers)', len(answers))    biran
            # print("idb['label']:",answers)
            label = self.get_soft_target(answers)
            # print("idb['label']:", label)
        
        w0, h0 = image.size
        if self.add_image_as_a_box:
            image_box = torch.as_tensor([[0.0, 0.0, w0 - 1, h0 - 1]])
            boxes = torch.cat((image_box, boxes), dim=0)
            if self.with_precomputed_visual_feat:
                image_box_feature = boxes_features.mean(0, keepdim=True)
                boxes_features = torch.cat((image_box_feature, boxes_features), dim=0)
        im_info = torch.tensor([w0, h0, 1.0, 1.0])
        flipped = False

        if self.transform is not None:#true,图片裁剪
            image, boxes, _, im_info, flipped = self.transform(image, boxes, None, im_info, flipped)

        w = im_info[0].item()
        h = im_info[1].item()
        boxes[:, [0, 2]] = boxes[:, [0, 2]].clamp(min=0, max=w - 1)
        boxes[:, [1, 3]] = boxes[:, [1, 3]].clamp(min=0, max=h - 1)

        # print("image.device:", image.device)
        if self.with_precomputed_visual_feat:
            boxes = torch.cat((boxes, boxes_features), dim=-1)
        # print("boxes.shape:", boxes.shape)

        if self.test_mode:
            return image, boxes, im_info, text_ids
        else:
            #print("image,boxes,text_ids,label", np.array(image).shape,np.array(boxes).shape,np.array(text_ids).shape,np.array(label).shape)
            return image, boxes, im_info, text_ids, label

    @staticmethod
    def flip_tokens(tokens, verbose=True):
        changed = False
        tokens_new = [tok for tok in tokens]
        for i, tok in enumerate(tokens):
            if tok == 'left':
                tokens_new[i] = 'right'
                changed = True
            elif tok == 'right':
                tokens_new[i] = 'left'
                changed = True
        if verbose and changed:
            logging.info('[Tokens Flip] {} -> {}'.format(tokens, tokens_new))
        return tokens_new

    @staticmethod
    def b64_decode(string):
        return base64.decodebytes(string.encode())

    def answer_to_ind(self, answer):
        # print("answer:",answer)
        # if answer in self.answer_vocab:
        #     return self.answer_vocab.index(answer)
        # else:
        #     return self.answer_vocab.index('<unk>')
        if answer == "real":
            return self.answer_vocab.index('real')
        else:
            return self.answer_vocab.index('fake')

    def get_soft_target(self, answers):

        soft_target = torch.zeros(len(self.answer_vocab), dtype=torch.float)
        answer_indices = [self.answer_to_ind(answer) for answer in answers]
        soft_target[answer_indices]=1

        return soft_target

    def processPunctuation(self, inText):

        if inText == '<unk>':
            return inText

        outText = inText
        for p in self.punct:
            if (p + ' ' in inText or ' ' + p in inText) or (re.search(self.commaStrip, inText) != None):
                outText = outText.replace(p, '')
            else:
                outText = outText.replace(p, ' ')
        outText = self.periodStrip.sub("",
                                       outText,
                                       re.UNICODE)
        return outText

    def load_annotations(self):
        tic = time.time()
        database = []
        if self.use_imdb:
            db_cache_name = 'vqa2_imdb_boxes{}_{}'.format(self.boxes, '+'.join(self.image_sets))
        else:
            db_cache_name = 'vqa2_nonimdb_boxes{}_{}'.format(self.boxes, '+'.join(self.image_sets))
        if self.with_precomputed_visual_feat:
            db_cache_name += 'visualprecomp'
        if self.zip_mode:
            db_cache_name = db_cache_name + '_zipmode'
        if self.test_mode:
            db_cache_name = db_cache_name + '_testmode'
        db_cache_root = os.path.join(self.root_path, 'cache')
        db_cache_path = os.path.join(db_cache_root, '{}.pkl'.format(db_cache_name))

        if os.path.exists(db_cache_path):
            if not self.ignore_db_cache:
                # reading cached database
                print('cached database found in {}.'.format(db_cache_path))
                with open(db_cache_path, 'rb') as f:
                    print('loading cached database from {}...'.format(db_cache_path))
                    tic = time.time()
                    database = cPickle.load(f)
                    print('Done (t={:.2f}s)'.format(time.time() - tic))
                    
                    drop_count = len(database) % self.batch_size
                    database = database[0:len(database) - drop_count]
                    print("{} were dropped to align with {}".format(drop_count, self.batch_size))
                    return database
            else:
                print('cached database ignored.')

        # ignore or not find cached database, reload it from annotation file
        print('loading database of split {}...'.format('+'.join(self.image_sets)))
        tic = time.time()

        count_noexists_box = 0

        for image, tweets_file, box_path in zip(self.image_fn, self.tweets_fn, self.box_fn):
            # tweets_data=self._load_tweets(tweets_file)
            tweets_data= pd.read_csv(tweets_file,index_col=False,error_bad_lines=False)
            #找到imageId和imagefile.name的对应关系
            print("tweets_data.len:",len(tweets_data))

            id2filename={}
            image_name = ['.jpg', '.png', '.jpeg', '.gif', '.JPG', '.PNG', '.jpe', '.tiff']
            image_list = os.listdir(image)
            for it in tweets_data.values:
                imageId = str(it[2])
                for suffix in image_name:
                    if imageId + suffix in image_list:
                        id2filename[imageId] = imageId + suffix
            #加载database
            for it in tweets_data.values:
                # print("imageId:", it[2])
                # print("text:", len(it[1]))
                # print("label:", it[3])
                box_fn = os.path.join(box_path, '{}.json'.format(str(it[2])))
                # print("box_fn",box_fn)
                text = it[1]
                if len(text)>500:
                    text = it[1][:500]
                if os.path.exists(box_fn):  # 如果box_fn存在
                    # print("box_fn,exists:", box_fn)print("box_fn,exists:", box_fn)
                    boxes_data = self._load_json(box_fn)
                    idb = {'image_id': str(it[2]),
                           'image_fn': os.path.join(image, id2filename[str(it[2])]),
                           'width': boxes_data["image_w"],
                           'height': boxes_data["image_h"],
                           'box_fn': box_fn,
                           'text': text,
                           'label': it[3]  if not self.test_mode else None
                           }
                    database.append(idb)
                else:
                    # print("cant find box_fn:", box_fn)
                    count_noexists_box = count_noexists_box+1

        print('count_noexists_box:',count_noexists_box)
        print('Done (t={:.2f}s)'.format(time.time() - tic))

        # cache database via cPickle
        if self.cache_db:
            print('caching database to {}...'.format(db_cache_path))
            tic = time.time()
            if not os.path.exists(db_cache_root):
                makedirsExist(db_cache_root)
            with open(db_cache_path, 'wb') as f:
                cPickle.dump(database, f)
            print('Done (t={:.2f}s)'.format(time.time() - tic))

        print("len(database):",len(database))
        drop_count = len(database) % self.batch_size
        if drop_count != 0:
            database = database[0:len(database) - drop_count]
            print("{} were dropped to align with {}".format(drop_count, self.batch_size))
                    
        return database

    @staticmethod
    def group_aspect(database):
        print('grouping aspect...')
        t = time.time()

        # get shape of all images
        widths = torch.as_tensor([idb['width'] for idb in database])
        heights = torch.as_tensor([idb['height'] for idb in database])

        # group
        group_ids = torch.zeros(len(database))
        horz = widths >= heights
        vert = 1 - horz
        group_ids[horz] = 0
        group_ids[vert] = 1

        print('Done (t={:.2f}s)'.format(time.time() - t))

        return group_ids

    def load_precomputed_boxes(self, box_file):
        if box_file in self.box_bank:
            return self.box_bank[box_file]
        else:
            in_data = {}
            with open(box_file, "r") as tsv_in_file:
                reader = csv.DictReader(tsv_in_file, delimiter='\t', fieldnames=FIELDNAMES)
                for item in reader:
                    item['image_id'] = int(item['image_id'])
                    item['image_h'] = int(item['image_h'])
                    item['image_w'] = int(item['image_w'])
                    item['num_boxes'] = int(item['num_boxes'])
                    for field in (['boxes', 'features'] if self.with_precomputed_visual_feat else ['boxes']):
                        item[field] = np.frombuffer(base64.decodebytes(item[field].encode()),
                                                    dtype=np.float32).reshape((item['num_boxes'], -1))
                    in_data[item['image_id']] = item
            self.box_bank[box_file] = in_data
            return in_data

    def __len__(self):
        return len(self.database)

    def _load_image(self, path):
        if '.zip@' in path:
            return self.zipreader.imread(path).convert('RGB')
        else:
            return Image.open(path).convert('RGB')

    def _load_json(self, path):
        if '.zip@' in path:
            f = self.zipreader.read(path)
            return json.loads(f.decode())
        else:
            with open(path, 'r') as f:
                return json.load(f)

    # def _read_text_with_label(self, path):
    #     text_with_label = []
    #     file = open(path)
    #     for line in file:
    #         data = []
    #         line_list = line.split('\t')
    #         #tweetId	tweetText	userId	imageId(s)	username	timestamp	label
    #         for i in line_list:
    #             data.append(i)
    #         text_with_label.append(data)
    #     file.close()
    #     return text_with_label

    def _load_tweets(self, path):
        text_with_label = []
        file = open(path)
        for i,line in enumerate(file):
            if i!=0:
                data = []
                line_list = line.split('\t')
                # tweetId	tweetText	userId	imageId(s)	username	timestamp	label
                for i in line_list:
                    data.append(i)
                text_with_label.append(data)
        file.close()
        return text_with_label

